﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using CSW.Framework.Common;
using CSW.Framework.Linq.SchemaModel;

namespace CSW.Framework.Linq.QueryModel
{
    /// <summary>
    /// Dynamic Query.
    /// </summary>
    public class Query
    {
        private readonly List<Property> m_Properties = new List<Property>();
        private readonly List<Container> m_ContainerList = new List<Container>();
        private readonly List<string> m_ExcludedFields = new List<string>();
        private string m_QuerySql;
        private Dictionary<Container, List<Property>> m_Containers = new Dictionary<Container, List<Property>>();
        private readonly Container m_MainContainer;
        private readonly List<object> m_Where = new List<object>();
        private readonly List<object> m_Take = new List<object>();
        private readonly List<OrderBy> m_OrderBy = new List<OrderBy>();
        private bool m_IsQueryGenerated;
        private QueryElementType m_Mode;
        private readonly Database m_Schema;
        private int m_ProjectionLevel = 1;
        private int m_CurrentProjectionLevel;
        private bool m_UseNoLock;
        //private int m_HashCode;

        /// <summary>
        /// Initializes a new instance of the <see cref="Query"/> class.
        /// </summary>
        /// <param name="schema">The schema.</param>
        /// <param name="mainTable">The main table.</param>
        public Query(Database schema, Table mainTable)
        {
            m_MainContainer = new Container(null, mainTable);
            m_ContainerList.Add(m_MainContainer);
            m_Mode = QueryElementType.None;
            m_Schema = schema;

            m_CurrentProjectionLevel = m_ProjectionLevel;
            AddProperties(m_MainContainer);

            //ObjectCounter.Add(this);
        }

        /*/// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="Query"/> is reclaimed by garbage collection.
        /// </summary>
        ~Query()
        {
            ObjectCounter.Remove(this);
        }*/

        /// <summary>
        /// Gets or sets the users. (debug)
        /// </summary>
        /// <value>The users.</value>
        public int Users { get; internal set; }

        private void AddProperties(Container container)
        {
            Guard.ArgumentNotNull(container, "container");
            Guard.ArgumentNotNull(container.Table, "container.Table");
            Guard.ArgumentNotNull(container.Table.Columns, "container.Table.Columns");

            bool greedy = (m_CurrentProjectionLevel > 0);
            if (greedy)
                m_CurrentProjectionLevel--;
            int savedCurrentProjectionLevel = m_CurrentProjectionLevel;

            Table table = container.Table;
            foreach (TableColumn column in table.Columns)
            {
                Property property = AddProperty(container, column, null);

                if (column.FKTable != null)
                {
                    bool addContainer = true;
                    if (container.IsReverse)
                    {
                        // exclude the foreign key that got us here (reverse lookup)
                        Table sourceTable = container.JoinColumn.FKTable;
                        if (column.FKTable == sourceTable)
                            addContainer = false;
                    }

                    if (addContainer)
                    {
                        Container fkContainer = AddContainer(property, column.FKTable);

                        // Add first level of foreign key values to reduce trips to the DB
                        if (greedy)
                        {
                            AddProperties(fkContainer);
                            m_CurrentProjectionLevel = savedCurrentProjectionLevel;
                        }
                    }
                }
            }

            foreach (TableProperty tableProperty in table.Properties)
            {
                if (tableProperty.IsChildContainer && !tableProperty.IsChildList)
                {
                    // virtual foreign key by unique constraint
                    // ReSharper disable AccessToModifiedClosure
                    Table childTable = m_Schema.Tables.Where(p => p.FullClassName == tableProperty.ChildDataType).Single();
                    TableColumn childColumn = childTable.Columns.Where(p => p.ColumnName == tableProperty.ChildColumnName).Single();
                    // ReSharper restore AccessToModifiedClosure

                    // get current table's PK
                    List<Property> props = GetPropertiesBeforeQuery(container).ToList();
                    Property pkProp = props.Where(p => p.TableColumn.IsKey).Single();

                    Container fkContainer = AddContainer(pkProp, childTable, childColumn);
                    AddProperty(fkContainer, childColumn, null);

                    if (greedy)
                    {
                        AddProperties(fkContainer);
                        m_CurrentProjectionLevel = savedCurrentProjectionLevel;
                    }
                }
            }
        }

        internal void SetMode(QueryElementType mode)
        {
            m_Mode = mode;
        }

        internal QueryElementType GetMode()
        {
            return m_Mode;
        }

        /// <summary>
        /// Adds the container.
        /// </summary>
        /// <param name="parentProperty">The parent property.</param>
        /// <param name="table">The table.</param>
        /// <returns>The container.</returns>
        public Container AddContainer(Property parentProperty, Table table)
        {
            Guard.ArgumentNotNull(parentProperty, "parentProperty");
            Guard.ArgumentNotNull(table, "table");

            Container newContainer = new Container(parentProperty, table);

            foreach (Container container in m_ContainerList)
            {
                if (container.Table == table &&
                    container.ParentProperty == parentProperty &&
                    container.JoinColumn == newContainer.JoinColumn)
                {
                    return container;
                }
            }

            m_ContainerList.Add(newContainer);

            return newContainer;
        }

        /// <summary>
        /// Adds the container.
        /// </summary>
        /// <param name="parentProperty">The parent property.</param>
        /// <param name="table">The table.</param>
        /// <param name="joinColumn">The join column.</param>
        /// <returns>The container.</returns>
        public Container AddContainer(Property parentProperty, Table table, TableColumn joinColumn)
        {
            Guard.ArgumentNotNull(parentProperty, "parentProperty");
            Guard.ArgumentNotNull(table, "table");
            Guard.ArgumentNotNull(joinColumn, "joinColumn");

            Container newContainer = new Container(parentProperty, table, joinColumn);

            foreach (Container container in m_ContainerList)
            {
                if (container.Table == table &&
                    container.ParentProperty == parentProperty &&
                    container.JoinColumn == newContainer.JoinColumn)
                {
                    return container;
                }
            }

            m_ContainerList.Add(newContainer);

            return newContainer;
        }

        /// <summary>
        /// Adds a property to the query.
        /// </summary>
        public Property AddProperty(Container container, TableColumn column, ExpressionType? nodeType)
        {
            Guard.ArgumentNotNull(container, "container");
            Guard.ArgumentNotNull(column, "column");

            if (m_IsQueryGenerated)
                throw new InvalidOperationException("Properties cannot be added once the query has been generated.");

            bool isDescending;

            foreach (Property property in m_Properties)
            {
                if (property.Container == container &&
                    property.TableColumn == column)
                {
                    if (column.FKTable == null) // todo: hack
                    {
                        if (GetIsOrderBy(out isDescending))
                        {
                            m_OrderBy.Add(new OrderBy(property, isDescending));
                        }
                    }

                    return property;
                }
            }

            if (!container.Table.Columns.Contains(column))
            {
                throw new Exception(string.Format("'{0}' does not belong to '{1}'.", column, container.Table));
            }

            Property newProperty = new Property { Container = container, TableColumn = column };

            m_Properties.Add(newProperty);

            if (column.FKTable == null) // todo: hack
            {
                if (GetIsOrderBy(out isDescending))
                {
                    m_OrderBy.Add(new OrderBy(newProperty, isDescending));
                }
            }

            return newProperty;
        }

        private bool GetIsOrderBy(out bool isDescending)
        {
            isDescending = false;
            switch (m_Mode)
            {
                case QueryElementType.OrderBy:
                    return true;

                case QueryElementType.OrderByDescending:
                    isDescending = true;
                    return true;

                default:
                    return false;
            }
        }

        private class OrderBy
        {
            public OrderBy(Property property, bool isDescending)
            {
                Property = property;
                IsDescending = isDescending;
            }

            public Property Property { get; private set; }
            public bool IsDescending { get; private set; }
        }

        /// <summary>
        /// Gets the main container.
        /// </summary>
        /// <value>The main container.</value>
        public Container MainContainer
        {
            get { return m_MainContainer; }
        }

        /*/// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
        /// </returns>
        /// <exception cref="T:System.NullReferenceException">The <paramref name="obj"/> parameter is null.</exception>
        public override bool Equals(object obj)
        {
            Query query = obj as Query;
            if (query == null)
                return false;

            if (this == query)
                return true;

            if (GetQuery() == query.GetQuery())
                return true;
            else
                return false;
        }

        /// <summary>
        /// Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        public override int GetHashCode()
        {
            if (!m_IsQueryGenerated)
                GetQuery();

            if (m_HashCode == 0)
                return base.GetHashCode();
            else
                return m_HashCode;
        }*/

        /// <summary>
        /// Gets the query string.
        /// </summary>
        /// <returns>The query string.</returns>
        public string GetQuery()
        {
            if (!m_IsQueryGenerated)
            {
                m_Mode = QueryElementType.None;
                m_Containers = new Dictionary<Container, List<Property>>();

                foreach (Property property in new List<Property>(m_Properties))
                {
                    if (property.Container == m_MainContainer)
                    {
                        // TODO: This is using ColumnName, not class PropertyName like it should
                        // FIX
                        if (m_ExcludedFields.Contains(property.TableColumn.ColumnName))
                            m_Properties.Remove(property);
                    }
                }

                // Build Container/List<Property> dictionary
                foreach (Property item in m_Properties)
                {
                    List<Property> properties;
                    if (!m_Containers.TryGetValue(item.Container, out properties))
                    {
                        properties = new List<Property>();
                        m_Containers.Add(item.Container, properties);
                    }

                    properties.Add(item);

                    if (item.Container.ParentProperty == null)
                    {
                        if (m_MainContainer != item.Container)
                        {
                            throw new Exception("One or more Containers are missing a ParentProperty");
                        }
                    }
                }

                // Get containers which do not have any columns specifically selected
                foreach (Container container in m_ContainerList)
                {
                    List<Property> properties;
                    if (!m_Containers.TryGetValue(container, out properties))
                    {
                        properties = new List<Property>();
                        m_Containers.Add(container, properties);
                    }
                }

                // Add any missing primary keys to m_Properties
                AddPrimaryKeys(m_Containers);

                string joins = BuildJoins(m_MainContainer, m_Containers.Keys, m_UseNoLock);

                StringBuilder sql = new StringBuilder();
                sql.AppendLine("SELECT");

                int? take = GetTake();
                if (take != null)
                    sql.AppendLine(string.Format("TOP {0}", take));

                bool addComma = false;
                for (int i = 0; i < m_Properties.Count; i++)
                {
                    Property property = m_Properties[i];

                    // TODO: no longer using IsChildContainer
                    //if (!property.TableColumn.IsChildContainer)
                    {
                        string tableAlias = property.Container.TableAlias;
                        string returnedColumnName = string.Format("{0}__{1}", tableAlias, property.TableColumn.ColumnName);
                        property.ReturnedColumnName = returnedColumnName;

                        if (addComma)
                            sql.AppendLine(",");
                        sql.Append(string.Format("{0}.[{1}] [{2}]", tableAlias, property.TableColumn.ColumnName, returnedColumnName));
                        addComma = true;
                    }
                }
                sql.AppendLine();
                sql.Append(joins);

                if (m_Where.Count != 0)
                {
                    sql.AppendLine("WHERE");

                    StringBuilder sqlWhere = new StringBuilder();
                    foreach (object item in m_Where)
                    {
                        if (item is Property)
                        {
                            Property property = (Property)item;
                            sqlWhere.Append(string.Format(" {0}.[{1}] ", property.Container.TableAlias, property.TableColumn.ColumnName));
                        }
                        else if (item is string)
                        {
                            if ((string)item == "AND" || (string)item == "OR")
                                sqlWhere.AppendLine();
                            sqlWhere.Append(string.Format(" {0} ", item));
                        }
                        else if (item is int)
                        {
                            sqlWhere.Append(string.Format(" {0} ", item));
                        }
                        else
                        {
                            throw new Exception(string.Format("Unhandled where type: {0}", item.GetType()));
                        }
                    }

                    string where = sqlWhere.ToString();

                    int whereLength;
                    do
                    {
                        whereLength = where.Length;

                        where = where.Replace("  ", " ");
                        where = where.Replace("( ", "(");
                        where = where.Replace(" )", ")");
                        where = where.Replace(" \r", "\r");
                        where = where.Replace("\n ", "\n");

                        int openIndex = where.IndexOf('(');
                        while (openIndex >= 0)
                        {
                            for (int i = openIndex + 1; i < where.Length; i++)
                            {
                                if (where[i] == '(')
                                {
                                    break;
                                }
                                else if (where[i] == ')')
                                {
                                    string where2 = where.Substring(openIndex + 1, i - openIndex - 1);

                                    int spaceIndex = where2.IndexOf(' ');
                                    if (spaceIndex != -1)
                                    {
                                        string tail = where2.Substring(spaceIndex + 1);
                                        if (tail != "IS NULL" &&
                                            tail != "IS NOT NULL" &&
                                            tail != "= 1" &&
                                            tail != "= 0")
                                        {
                                            break;
                                        }
                                    }

                                    string where1 = openIndex > 0 ? where.Substring(0, openIndex) : string.Empty;
                                    string where3 = where.Substring(i + 1);

                                    where = where1 + where2 + where3;
                                    openIndex = -1;
                                    break;
                                }
                            }
                            openIndex = where.IndexOf('(', openIndex + 1);
                        }
                    } while (whereLength != where.Length);

                    where = where.Trim();

                    // TODO: temp hack, doesn't like situation like:
                    // TODO: .Where(p => p.blahblah == yackityshmackity && localVal) <- the bool localVar throws it off
                    where = where.Replace("AND @p0)", "AND @p0 = 1)");
                    where = where.Replace("AND @p1)", "AND @p1 = 1)");
                    where = where.Replace("OR @p0)", "OR @p0 = 1)");
                    where = where.Replace("OR @p1)", "OR @p1 = 1)");

                    sql.Append(where);
                }

                if (m_OrderBy.Count != 0)
                {
                    sql.AppendLine();
                    sql.AppendLine("ORDER BY");
                    addComma = false;
                    for (int i = 0; i < m_OrderBy.Count; i++)
                    {
                        OrderBy orderBy = m_OrderBy[i];
                        if (addComma)
                            sql.AppendLine(",");
                        string desc = orderBy.IsDescending ? " DESC" : "";
                        sql.Append(string.Format("{0}.[{1}]{2}", orderBy.Property.Container.TableAlias, orderBy.Property.TableColumn.ColumnName, desc));
                        addComma = true;
                    }
                    sql.AppendLine();
                }

                m_QuerySql = sql.ToString().Replace("\n ", "\n");

                //m_HashCode = CRC32.CalculateInt32(Encoding.ASCII.GetBytes(m_QuerySql));

                m_IsQueryGenerated = true;
            }

            return m_QuerySql;
        }

        private int? GetTake()
        {
            if (m_Take.Count != 0)
            {
                if (m_Take.Count != 1)
                    throw new InvalidOperationException("More than one Take called.");

                if (!(m_Take[0] is int))
                {
                    throw new InvalidOperationException("Take must be numeric.");
                }
                else
                {
                    int take = (int)m_Take[0];
                    if (take < 0)
                        throw new InvalidOperationException("Take must greater than or equal to 0.");
                    return take;
                }
            }
            else
            {
                return null;
            }
        }

        private void AddPrimaryKeys(IEnumerable<KeyValuePair<Container, List<Property>>> containers)
        {
            foreach (KeyValuePair<Container, List<Property>> kvp in containers)
            {
                Container container = kvp.Key;
                List<Property> properties = kvp.Value;

                TableColumn[] primaryKey = container.Table.PrimaryKey;

                TableColumn[] existingKeys = properties.Where(p => primaryKey.Contains(p.TableColumn)).Select(p => p.TableColumn).ToArray();

                TableColumn[] missingKeys = primaryKey.Where(p => !existingKeys.Contains(p)).ToArray();

                foreach (TableColumn addColumn in missingKeys)
                {
                    Property property = new Property();
                    property.TableColumn = addColumn;
                    property.Container = container;

                    m_Properties.Add(property);
                    properties.Add(property);
                }
            }
        }

        private static string BuildJoins(Container mainContainer, IEnumerable<Container> containers, bool noLock)
        {
            int aliasIndex = 0;
            return BuildJoins(mainContainer, containers, noLock, ref aliasIndex);
        }

        private static string BuildJoins(Container mainContainer, IEnumerable<Container> containers, bool noLock, ref int aliasIndex)
        {
            Guard.ArgumentNotNull(mainContainer, "mainContainer");
            Guard.ArgumentNotNull(containers, "containers");

            List<Container> joins = new List<Container>();

            string queryHint = noLock ? " (nolock)" : string.Empty;

            StringBuilder sql = new StringBuilder();
            if (mainContainer.ParentProperty == null)
            {
                string tableAlias = string.Format("T{0}", aliasIndex);
                mainContainer.TableAlias = tableAlias;
                aliasIndex++;

                sql.AppendLine(string.Format("FROM {0} {1}{2}", GetSqlTableName(mainContainer), tableAlias, queryHint));
            }

            foreach (Container joinContainer in containers)
            {
                if (mainContainer == joinContainer)
                    continue;

                Property parentProperty = joinContainer.ParentProperty;
                if (parentProperty != null)
                {
                    if (parentProperty.Container == mainContainer)
                    {
                        string tableAlias = string.Format("T{0}", aliasIndex);
                        joinContainer.TableAlias = tableAlias;
                        aliasIndex++;

                        /*TableColumn[] primaryKeys = joinContainer.Table.PrimaryKey;
                        if (primaryKeys.Length == 0)
                            throw new Exception(string.Format("No primary key found on '{0}'.", joinContainer.Table));
                        else if (primaryKeys.Length > 1)
                            throw new NotImplementedException(string.Format("Composite primary key found on '{0}'. Composite keys are not supported.", joinContainer.Table));
                        TableColumn primaryKey = primaryKeys[0];*/

                        sql.AppendLine(string.Format("LEFT JOIN {0} {1}{2}", GetSqlTableName(joinContainer), tableAlias, queryHint));
                        sql.AppendLine(string.Format("ON {0}.[{1}] = {2}.[{3}]", mainContainer.TableAlias, parentProperty.TableColumn.ColumnName, tableAlias, joinContainer.JoinColumn.ColumnName));

                        joins.Add(joinContainer);
                    }
                }
            }

            foreach (Container joinContainer in joins)
            {
                string additionalJoinSql = BuildJoins(joinContainer, containers, noLock, ref aliasIndex);
                sql.Append(additionalJoinSql);
            }

            return sql.ToString();
        }

        private static string GetSqlTableName(Container container)
        {
            Guard.ArgumentNotNull(container, "container");
            Guard.ArgumentNotNull(container.Table, "container.Table");

            if (!string.IsNullOrEmpty(container.Table.Schema))
                return string.Format("[{0}].[{1}]", container.Table.Schema, container.Table.TableName);
            else
                return string.Format("[{0}]", container.Table.TableName);
        }

        internal string GetReturnedColumnName(Container container, TableColumn column)
        {
            if (container == null)
                throw new ArgumentNullException("container", "Parameter 'container' cannot be null.");
            if (column == null)
                throw new ArgumentNullException("column", "Parameter 'column' cannot be null.");

            List<Property> properties = m_Containers[container];
            foreach (Property property in properties)
            {
                if (property.TableColumn == column)
                    return property.ReturnedColumnName;
            }

            throw new InvalidOperationException(string.Format("TableColumn '{0}' not found in '{1}'.", column, container.Table));
        }

        internal IEnumerable<Property> GetPropertiesBeforeQuery(Container container)
        {
            // this is garbage .. must refactor

            Guard.ArgumentNotNull(container, "container");

            List<Property> props = new List<Property>();
            foreach (Property prop in m_Properties)
            {
                if (prop.Container == container)
                    props.Add(prop);
            }

            return props;
        }

        internal IEnumerable<Property> GetProperties(Container container)
        {
            if (container == null)
                throw new ArgumentNullException("container", "Parameter 'container' cannot be null.");

            return m_Containers[container];
        }

        private readonly Dictionary<Container, List<Container>> m_VirtualContainerDictionary = new Dictionary<Container, List<Container>>();

        internal List<Container> GetVirtualContainers(Container container)
        {
            if (container == null)
                throw new ArgumentNullException("container", "Parameter 'container' cannot be null.");

            List<Container> virtualContainers;
            if (!m_VirtualContainerDictionary.TryGetValue(container, out virtualContainers))
            {
                virtualContainers = new List<Container>();
                foreach (KeyValuePair<Container, List<Property>> kvp in m_Containers)
                {
                    Container referencingContainer = kvp.Key;
                    if (referencingContainer == container)
                        continue;
                    if (referencingContainer.ParentProperty == null)
                        continue;
                    if (!referencingContainer.IsReverse)
                        continue;

                    if (referencingContainer.ParentProperty.Container == container)
                    {
                        virtualContainers.Add(referencingContainer);
                    }
                }

                m_VirtualContainerDictionary.Add(container, virtualContainers);
            }

            return virtualContainers;
        }

        internal bool TryGetContainerLinkedTo(Property property, out Container container)
        {
            if (property == null)
                throw new ArgumentNullException("property", "Parameter 'property' cannot be null.");

            foreach (Container tmpContainer in m_Containers.Keys)
            {
                if (tmpContainer.ParentProperty == property)
                {
                    container = tmpContainer;
                    return true;
                }
            }

            container = null;
            return false;
        }

        internal void AddWhere(Property property)
        {
            switch (m_Mode)
            {
                case QueryElementType.Where:
                    m_Where.Add(property);
                    break;

                case QueryElementType.OrderBy:
                case QueryElementType.OrderByDescending:
                    break;

                default:
                    throw new InvalidOperationException(string.Format("Method call is not valid for mode '{0}'.", m_Mode));
            }
        }

        internal bool HasWhere()
        {
            return (m_Where.Count != 0);
        }

        private bool ignoreNextClose;

        internal void AddWhere(string op)
        {
            bool resetNextClose = true;

            switch (m_Mode)
            {
                case QueryElementType.Where:
                    if (string.Compare(op, "NULL", true) == 0)
                    {
                        int i = m_Where.Count - 1;
                        string prevStr = m_Where[i].ToString();
                        if (prevStr == "(")
                        {
                            prevStr = m_Where[m_Where.Count - 2].ToString();

                            if (prevStr == "=" || prevStr == "!=" || prevStr == "<>")
                            {
                                m_Where.RemoveAt(m_Where.Count - 2);

                                i = m_Where.Count - 1;

                                if (prevStr == "=")
                                    m_Where[i] = "IS";
                                else if (prevStr == "!=" || prevStr == "<>")
                                    m_Where[i] = "IS NOT";

                                ignoreNextClose = true;
                                resetNextClose = false;
                            }

                        }
                        else
                        {
                            if (prevStr == "=")
                                m_Where[i] = "IS";
                            else if (prevStr == "!=" || prevStr == "<>")
                                m_Where[i] = "IS NOT";
                        }
                    }

                    if (!(ignoreNextClose && op == ")"))
                    {
                        m_Where.Add(op);
                    }

                    if (resetNextClose)
                        ignoreNextClose = false;

                    break;

                default:
                    throw new InvalidOperationException(string.Format("Method call is not valid for mode '{0}'.", m_Mode));
            }
        }

        internal void AddWhere(int op)
        {
            switch (m_Mode)
            {
                case QueryElementType.Where:
                    m_Where.Add(op);
                    break;

                case QueryElementType.Take:
                    m_Take.Add(op);
                    break;

                default:
                    throw new InvalidOperationException(string.Format("Method call is not valid for mode '{0}'.", m_Mode));
            }
        }

        internal void SetTakeForSingleCall(int count)
        {
            int? take = GetTake();
            if (take == null || take > 2)
            {
                m_Take.Clear();
                m_Take.Add(count);
            }
        }

        internal void SetProjectionLevel(int levels)
        {
            if (levels < m_ProjectionLevel)
                throw new InvalidOperationException(string.Format("Projection level for this query must be >= {0}", m_ProjectionLevel));

            m_ProjectionLevel = levels;
            m_CurrentProjectionLevel = m_ProjectionLevel;
            AddProperties(m_MainContainer);
        }

        internal void SetNoLock(bool value)
        {
            m_UseNoLock = value;
        }

        internal void ChangeLast(string find, string replace)
        {
            for (int i = m_Where.Count - 1; i >= 0; i--)
            {
                string where = m_Where[i].ToString();
                int idx = where.ToUpper().IndexOf(find.ToUpper());
                if (idx >= 0)
                {
                    //string oldWhere = where;
                    where = string.Format("{0}{1}{2}", idx != 0 ? where.Substring(0, idx) : "", replace, where.Substring(idx + find.Length));
                    m_Where[i] = where;
                    return;
                }
            }
            throw new Exception(string.Format("{0} not found", find));
        }

        internal void ExcludeField(Expression expression)
        {
            string fieldName = LinqToSqlHelper.GetFieldName((LambdaExpression)expression);
            m_ExcludedFields.Add(fieldName);
        }
    }
}
